import { EARTH_RADIUS, IS_NODE } from "./Constant"
export function extend(dest) {
  for (let i = 1; i < arguments.length; i++) {
    const src = arguments[i];
    for (const k in src) {
      dest[k] = src[k];
    }
  }
  return dest;
}

export function check(v, d) {
  return v === undefined || v === "undefined" ? d : v;
}

export function isNil(obj) {
  return obj == null;
}

export function isNumber(val) {
  return (typeof val === "number") && !isNaN(val);
}

export function isInteger(n) {
  return (n | 0) === n;
}

export function isObject(obj) {
  return typeof obj === "object" && !!obj;
}

export function isString(obj) {
  if (isNil(obj)) {
    return false;
  }
  return typeof obj === "string" || (obj.constructor !== null && obj.constructor === String);
}

export function isFunction(obj) {
  if (isNil(obj)) {
    return false;
  }
  return typeof obj === "function" || (obj.constructor !== null && obj.constructor === Function);
}

export function isArray(obj) {
  return Object.prototype.toString.call(obj) === "[object Array]";
}

export function deg2Rad(deg) {
  return deg / 180 * Math.PI
}

export function rad2Deg(rad) {
  return rad / Math.PI * 180
}

export function getBoundRect(path) {
  let minX = path[0].x
  let maxX = path[0].x
  let minY = path[0].y
  let maxY = path[0].y
  for (let i = 1, len = path.length; i < len; i++) {
    let pos = path[i]
    minX = Math.min(minX, pos.x)
    minY = Math.min(minY, pos.y)
    maxX = Math.max(maxX, pos.x)
    maxY = Math.max(maxY, pos.y)
  }
  return {
    minX, minY, maxX, maxY
  }
}

export function flatten(arr) {
  while (arr.some((item) => Array.isArray(item))) {
    arr = [].concat.apply([], arr)
  }
  return arr
}

export function clamp(n, min, max) {
  return Math.min(max, Math.max(min, n))
}

export function hexToRGB(hex) {
  var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
  return result ? {
    r: parseInt(result[1], 16),
    g: parseInt(result[2], 16),
    b: parseInt(result[3], 16)
  } : null
}

export function compareObject (obj1, obj2) {
  // 递归终止条件，当 obj1 或 obj2 不是对象时，此时就可以进行判断了
  if (typeof obj1 !== 'object' || typeof obj2 !== 'object') {
    if (obj1 === obj2) {
      return true
    } else if (obj1 !== obj2) {
      return false
    }
  }
  // 获取对象的自由属性组成的数组
  const obj1PropsArr = Object.getOwnPropertyNames(obj1)
  const obj2PropsArr = Object.getOwnPropertyNames(obj2) 
  // 如果数组的长度不相等，那么说明对象属性的个数都不同，返回 false
  if (obj1PropsArr.length !== obj2PropsArr.length) {
    return false
  }
  // 记录当前 compareObject 的返回值，默认是 true
  let status = true
  for (key of obj1PropsArr) {
    status = compareObject(obj1[key], obj2[key])
    // 关键代码，当 status 为 false 时下面就不用再进行判断了，说明两个对象的内容并不相同
    // 如果没有下面这条语句，那么只要对象底层的内容是相同的那么就返回 true
    if (!status) {
      break
    }
  }
  // 每次 compareObject 执行的返回结果
  return status
}

const hasOwnProperty = Object.prototype.hasOwnProperty;
export function hasOwn(obj, key) {
  return hasOwnProperty.call(obj, key);
}

let requestAnimFrame, cancelAnimFrame;
/* istanbul ignore next */
(function() {
  if (IS_NODE) {
    requestAnimFrame = function(fn) {
      return setTimeout(fn, 16)
    }

    cancelAnimFrame = clearTimeout
    return
  }

  let requestFn, cancelFn
  // slow down fps in IE9 for performance
  const timeToCall = 1000 / 30
  function timeoutDefer(fn) {
    return setTimeout(fn, timeToCall)
  }

  function getPrefixed(name) {
    return window['webkit' + name] || window['moz' + name] || window['ms' + name]
  }
  if (typeof (window) !== 'undefined') {
    requestFn = window.requestAnimationFrame || getPrefixed('RequestAnimationFrame') || timeoutDefer
    cancelFn = window.cancelAnimationFrame || getPrefixed('CancelAnimationFrame') ||
            getPrefixed('CancelRequestAnimationFrame') || function(id) { window.clearTimeout(id) }
  } else {
    requestFn = timeoutDefer
    cancelFn = clearTimeout
  }

  requestAnimFrame = function(fn) {
    return requestFn(fn)
  }

  cancelAnimFrame = function(id) {
    if (id) {
      cancelFn(id)
    }
  }
})()
export { requestAnimFrame, cancelAnimFrame }

export function lngLatToWorld(lngLat) {
  lngLat = [+lngLat[0], +lngLat[1]]
  const x = ((180 + lngLat[0]) % 360) / 360 * (2 * Math.PI * EARTH_RADIUS)
  const y = (180 - (180 / Math.PI * Math.log(Math.tan(Math.PI / 4 + lngLat[1] * Math.PI / 360)))) / 360 * (2 * Math.PI * EARTH_RADIUS)
  return [x, y]
}

export function worldToLngLat(world) {
  let x = world.x / (2 * Math.PI * EARTH_RADIUS) * 360 - 180
  if (x < -180) x += 360
  if (x > 180) x -= 360

  const _y = (180 - world.y / (2 * Math.PI * EARTH_RADIUS) * 360) * Math.PI / 180
  const _y2 = Math.pow(Math.E, _y)
  const _y3 = Math.atan(_y2)
  const y = (_y3 - Math.PI / 4) * 360 / Math.PI
  return [x, y]
}

export function transformCoordinate(center, aimPos) {
  return new THREE.Vector2(aimPos[0] - center[0], center[1] - aimPos[1])
}

export function unTransformCoordinate(center, aimPos) {
  return new THREE.Vector2(aimPos[0] + center[0], center[1] - aimPos[1])
}

function add(out, a, b) {
  out[0] = a[0] + b[0]
  out[1] = a[1] + b[1]
  return out
}

function set(out, x, y) {
  out[0] = x
  out[1] = y
  return out
}

function normalize(out, a) {
  const x = a[0]
  const y = a[1]
  let len = x * x + y * y
  if (len > 0) {
    // TODO: evaluate use of glm_invsqrt here?
    len = 1 / Math.sqrt(len)
    out[0] = a[0] * len
    out[1] = a[1] * len
  }
  return out
}

function subtract(out, a, b) {
  out[0] = a[0] - b[0]
  out[1] = a[1] - b[1]
  return out
}

function dot(a, b) {
  return a[0] * b[0] + a[1] * b[1]
}

function addNext(out, normal, length) {
  out.push([[normal[0], normal[1]], length])
}
const tmp = [0, 0]
function computeMiter(tangent, miter, lineA, lineB, halfThick) {
  add(tangent, lineA, lineB)
  normalize(tangent, tangent)

  // get miter as a unit vector
  set(miter, -tangent[1], tangent[0])
  set(tmp, -lineA[1], lineA[0])

  // get the necessary length of our miter
  return halfThick / dot(miter, tmp)
}
function normal(out, dir) {
  // get perpendicular
  set(out, -dir[1], dir[0])
  return out
}
function direction(out, a, b) {
  // get unit dir of two lines
  subtract(out, a, b)
  normalize(out, out)
  return out
}
const lineA = [0, 0]
const lineB = [0, 0]
const tangent = [0, 0]
const miter = [0, 0]
export function getNormals(points, closed) {
  let curNormal = null
  const out = []
  if (closed) {
    points = points.slice()
    points.push(points[0])
  }

  const total = points.length
  for (let i = 1; i < total; i++) {
    const last = points[i - 1]
    const cur = points[i]
    const next = i < points.length - 1 ? points[i + 1] : null

    direction(lineA, cur, last)
    if (!curNormal) {
      curNormal = [0, 0]
      normal(curNormal, lineA)
    }

    // add initial normals
    if (i === 1) { addNext(out, curNormal, 1) }

    if (!next) { // no miter, simple segment
      normal(curNormal, lineA) // reset normal
      addNext(out, curNormal, 1)
    } else { // miter with last
      // get unit dir of next line
      direction(lineB, next, cur)

      // stores tangent & miter
      const miterLen = computeMiter(tangent, miter, lineA, lineB, 1)
      addNext(out, miter, miterLen)
    }
  }

  // if the polyline is a closed loop, clean up the last normal
  if (points.length > 2 && closed) {
    const last2 = points[total - 2]
    const cur2 = points[0]
    const next2 = points[1]

    direction(lineA, cur2, last2)
    direction(lineB, next2, cur2)
    normal(curNormal, lineA)

    const miterLen2 = computeMiter(tangent, miter, lineA, lineB, 1)
    out[0][0] = miter.slice()
    out[total - 1][0] = miter.slice()
    out[0][1] = miterLen2
    out[total - 1][1] = miterLen2
    out.pop()
  }
  return out
}

export function polylineNormalToNormal(normal, s) {
  return [normal[0] * s, normal[1] * s]
}

export function mix(x, y, a) {
  return x * (1 - a) + y * a
}
